루비로 배우는 객체지향 디자인 | 3장 의존성 관리하기

날짜
May 18, 2024
태그
OOP
설명
 
잘 작성된 객체는 하나의 책임만을 지니고 있다. 그렇기에 객체가 복잡한 일을 수행하기 위해서는 다른 객체와 협업해야 한다. 서로 협업을 위해서는 객체는 다른 객체에 대한 지식이 있어야 한다. 지식은 의존성을 만들어 낸다. 이러한 의존성을 관리하지 못하면 애플리케이션은 엉망이 되고 만다.
 

의존성 이해하기

class Gear: def __init__(self, chainring, cog, rim, tire): self.chanring = chainring self.cog = cog self.rim = rim self.tire = tire def gear_inches(self): return ratio * Wheel(rim, tire).diameter def ratio(self): return chainring / float(cog) class Wheel: def __init__(self, rim, tire): self.rim = rim self.tire = tire def diameter(self): return rim + (tire * 2) Gear(52, 11, 26, 1.5).gear_inches
2장 단일책임원칙을 지키는 Gear, Wheel 클래스
 

의존성의 존재를 알기

의존성이 있는다는 것은 어떻게 파악할 수 있을까?
  • Gear는 Wheel이라는 이름의 클래스가 있다는 것을 알고 있다.
  • Gear는 Wheel이 diameter라는 메서드를 가지고 있는 것을 알고 있다.
  • Gear는 Wheel의 생성을 위해 rim, tire를 인자로 넘겨야 하는 것을 알고 있다.
  • Gear는 Wheel 인자의 순서를 알고 있다.
 
이러한 의존성은 Wheel이 변경됨에 따라 어쩔수 없이 Gear도 수정하게 만든다. 협업을 하기때문에 어느정도의 의존성이 생가는 것은 어쩔 수 없다. 하지만 위에서 이야기한 의존성은 불필요하다. 최소한의 지식만을 알고 그 외에는 모르도록 의존성을 관리해야 한다.
 

의존성 주입하기

class Gear: def __init__(self, chainring, cog, rim, tire): self.chanring = chainring self.cog = cog self.rim = rim self.tire = tire def gear_inches(self): return ratio * Wheel(rim, tire).diameter def ratio(self): return chainring / float(cog) Gear(52, 11, 26, 1.5).gear_inches
불필요한 의존성을 가지고 있는 Gear 클래스의 gear_inches 메소드
불필요한 의존성을 가진 Gear의 재사용성은 떨어지고, 너무 많은 것을 알고 있으면 덜 유용해진다.
 
class Gear: def __init__(self, chainring, cog, wheel): self.chanring = chainring self.cog = cog self.wheel = wheel def gear_inches(self): return ratio * wheel.diameter def ratio(self): return chainring / float(cog) Gear(52, 11, Wheel(26, 1.5)).gear_inches
의존성 주입으로 Gear는 Wheel에 대해 아는 것이 적어졌다.
의존성 주입을 통해 Gear와 Wheel의 결합을 끊었다. Gear는 diameter를 구현하고 있는 어떠한 객체와도 협업할 수 있게 되었다.
 

의존성 격리시키기

불필요한 의존성을 모두 제거하면 좋겠지만, 아쉽게도 기술적으론 가능할 지 몰라도 현실에서는 어렵다. 불필요한 의존성을 제거할 수 없는 경우라면 적어도 클래스 안에 격리시켜야 한다.
 

인스턴스 생성을 격리시키기

class Gear: def __init__(self, chainring, cog, rim, tire): self.chanring = chainring self.cog = cog self.wheel = Wheel(rim, tire) def gear_inches(self): return ratio * wheel.diameter Gear(52, 11, 26, 1.5).gear_inches
Wheel 클래스를 Gear 클래스 내부에 격리시켜 의존성을 낮추는 방법
 
class Gear: def __init__(self, chainring, cog, rim, tire): self.chanring = chainring self.cog = cog self.rim = rim self.tire = tire def gear_inches(self): return ratio * wheel.diameter def wheel(self): return Wheel(self.rim, self.tire) Gear(52, 11, 26, 1.5).gear_inches
wheel 메소드를 구현하고 메소드 내부에 격리시켜 의존성을 낮추는 방법
 
여전히 Gear에서 Wheel 인스턴스를 만들어야 하고 의존성 또한 높다. 하지만 gear_inches가 가지고 있던 의존성의 일부를 줄였고, Gear가 Wheel에 의존하고 있다는 사실을 명시적으로 드러낼 수 있었다. 이러한 변화는 재사용과 리팩터링을 수월하게 해준다.
 

외부로 전송되는 메시지를 격리하기

def gear_inches(self): return ratio * wheel.diameter
gear_inches 함수 - 내부와 외부로 보내지는 메시지
내부와 외부에 존재하는 의존성은 gear_inches 함수를 취약하게 만든다. 무엇을 수정하든 망가져버릴 위험이 높은 함수이다.
 
def gear_inches(self): foo = some_result * diameter ... def diameter(self): return wheel.diameter
diameter를 별도 메서드로 분리(격리)하여, 외부 객체의 메서드에 결합된 의존성을 줄였다.
Wheel 클래스에서 diameter를 바꾼다고 한다면, 그 파급은 이제 Gear 클래스의 작은 함수 단위인 diameter에 한정되어 영향을 미칠 것이다.
 

초기화 인자로 해시를 이용하기

class Gear: def __init__(self, **kwargs): self.chainring = kwargs.get('chainring', 40) # 기본값 40 self.cog = kwargs.get('cog', 16) # 기본값 16 Gear(chainring=52, cog=15)
키워드인자를 이용해서 인자 순서에 대한 의존성을 제거했다
 
class Gear: def __init__(self, *, chainring, cog): self.chanring = chainring self.cog = cog Gear(chainring=52, cog=15)
‘*’를 통해 명시적으로 키워드인자를 사용함을 나타내고 있다
 

멀티파라미터 초기화를 고립시키기

# 외부 프레임워크 모듈 # FrameworkGearModule은 건드릴 수 없는 외부 프레임워크이다. # init에는 순서가 고정된 인자들을 필요로 한다. class FrameworkGearModule: def __init__(self, chainring, cog, wheel): self.chainring = chainring self.cog = cog self.wheel = wheel # 팩토리 모듈을 생성해 외부 인터페이스를 감싸서 변화를 받아들일 수 있도록 만든다 class GearWrapper: @classmethod def gear(cls, args): return FrameworkGearModule(args['chainring'], args['cog'], args['wheel']) # 이제 키워드인자로 Gear 인스턴스를 생성할 수 있다. GearWrapper.gear(chainring=52, cog=11, wheel=Wheel(26, 1.5))
변경할 수 없는 외부 모듈애 대한 팩토리 모듈을 두어 키워드 인자를 사용해 Gear 인스턴스를 생성할 수 있다
외부에 대한 의존성을 메서드로 감싸고 고립시키는 방법을 통해서 우리가 작성한 코드에 의존하게 만든다.
 
 

의존성 방향 바꾸기

class Gear: def __init__(self, chainring, cog): self.chainring = chainring self.cog = cog def gear_inches(self, diameter): return ratio * diameter class Wheel: def __init__(self, rim, tire, chainring, cog): self.rim = rim self.tire = tire self.gear = Gear(chainring, cog) def diameter(self): return rim + (tire * 2) def gear_inches(self): return gear.gear_inches(diameter) Wheel(26, 1.5, 52, 11).gear_inches
의존성의 방향을 바꿨다
의존성의 방향을 바꾼다고 해서 지금 당장 크게 달라질 것은 없다. 영원히 변하지 않을 애플리케이션이라면 말이다.
 

의존성의 방향 결정하기

의존성 방향의 결정에 따라 유지보수가 쉬운 소프트웨어가 될 수 있고, 아니라면 점점 더 수정하기 어려운 소프트웨어가 될 수 있다.
 
의존성의 방향을 판단하는 세가지 진실이 있다.
  • 어떤 클래스는 다른 클래스에 비해 요구사항이 더 자주 바뀐다.
  • 구체클래스는 추상클래스보다 수정해야하는 경우가 빈번히 발생한다.
  • 의존성이 높은 클래스를 변경하는 것은 코드의 여러 곳에 영향을 미친다.
 
자기 자신보다 덜 변하는 코드에 의존하라고 말하고 있다.
 

변경될 가능성이 얼마나 높은지 이해하기

클래스는 항상 변경될 가능성이 있다. 애플리케이션에서 사용하는 모든 클래스는 ‘다른 클래스와 비교해 얼마나 변경되지 않는지’를 기준으로 순위를 매겨볼 수 있다.
 

구체적인 것과 추상적인 것을 인지하기

추상의 개념은 ‘모든 구체적인 것으로부터 분리된’의 의미를 지닌다. 이전에 Gear 클래스가 Wheel 클래스에 의존했던 상황은 구체적인 코드에 의존하고 있던 것이다. 이후 의존성 주입을 통해 wheel 인스턴스를 받도록 수정되면서 추상적인 코드에 의존하게 된 것이다.
 

요약

의존성 관리의 핵심은 그 방향을 관리하는 것이다. 평온한 유지보수라는 궁극의 목표를 향한 길은 자기 자신보다 덜 변하는 것에 의존하는 클래스들로 덮어있다.
 

댓글

guest